#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <math.h>

#include "parser.h"
#include "lista.h"
#include "termino.h"
#include "utilidades.h"
#include "abb_avl.h"

#define ERR_LECT -1
#define MIN_MI 10
/** 
 * TODO/Correcciones:
 * 
 * 		void actualizar_avl(abb_avl_t* avl,char* termino,char* ruta_archivo,long* cantidad_de_memoria_usada);
 **/


/*
 * Funcion auxiliar para incrementar enteros con puntero.
 * No devuelve nada.
 */
void incrementar_entero(int* entero){
	*entero = *entero + 1;
}

void incrementar_long(long* entero){
	*entero = *entero + 1;
}

void incrementar_memoria(long int* cantidad_actual,int incremento){
	*cantidad_actual = *cantidad_actual + incremento;
}


void wrapper_destruir_termino (void* termino){
	termino= (termino_t*)termino;
	termino_destruir(termino);
}


/* 
 *
 *	FUNCIONES
 *
 */
 
/*	
 *
 *	Dado numero de orden, devuelve el el siguiente subarchivo, ya abierto para escritura, 
 *	creandolo si hiciera falta.
 *
 */
FILE* obtener_subarchivo(int* nro_de_orden_del_subarchivo){
	
	//Reservo memoria para mas que el maximo string posible, el tamaño de un int x el de un char
	char nro_string [33];
	

	//Paso el numero a string
	sprintf(nro_string,"%d",*nro_de_orden_del_subarchivo); 
	
	//Construyo el nombre del subarchivo.
	char nombre_subarchivo [50]="";
	strcat(nombre_subarchivo,"subarchivo-");
	strcat(nombre_subarchivo,nro_string);
	strcat(nombre_subarchivo,".subarch");
	//Abro el archivo
	FILE* subarchivo;

	subarchivo=fopen(nombre_subarchivo,"w");
	if (!subarchivo){
		printf("ERROR DE LECTURA DEL DISCO");
		return NULL;
	}


	return subarchivo;
}

/*
 * Recibe información acerca del término, el documento de donde proviene
 * y la cantidad de repeticiones en el mismo. Concatena esta información
 * en un mismo string con formato característico y devuelve el string.
 */
char* pasar_termino_a_string(const char* termino,int* repeticiones, char* ID_documento){
	
	char nro_string[11];
	
	//Paso el numero a string
	sprintf(nro_string,"%d",*repeticiones); 
	
	size_t tamanio_termino = strlen(termino);
	
	int id_doc = atoi(ID_documento);
	char* nuevo_id_doc = (char*) malloc(6*sizeof(char));
	sprintf(nuevo_id_doc,"%05u",id_doc);

	char* buffer = malloc( (tamanio_termino+2+11+6+1 + strlen(ID_documento))* sizeof(char) ); //El termino + las 2 comas + el ID archivo +  el numero + el \n 
	if(!buffer)
		return NULL;
	strcpy(buffer,termino);
	strcat(buffer,",");
	strcat(buffer,nuevo_id_doc);
	strcat(buffer,",");
	strcat(buffer,nro_string);
	strcat(buffer,"\n");
	
	free(nuevo_id_doc);
	
	return buffer;
}

/*
 * Determina si un termino tiene que se considerado como tal por el
 * parser o no. Devuelve true si es un termino o false si no lo es.
 */
bool considerar_termino(termino_t* termino){
	
	/*
	 * Si no alcanza el minimo valor del MI, no es un termino.
	 */
	if( termino->mi < MIN_MI ) return false;
	
	return true;
	
}

/*
 * Recibe el ID_del_termino, el documento de donde viene y la estructura
 * en el mismo. Ademas recibe un archivo abierto en el cual se escribe la
 * nueva entrada. El formato de la entrada se especifica en la función llamada.
 */
bool pasar_termino_a_subarchivo(const char* termino, void* estructura_termino, void* archivo, void* ID_documento){
	
	//Casteos necesarios, ya que el avl recibe void*
	archivo=(FILE*)archivo;
	
	//Uso un aux a causa del magico C
	termino_t* aux =(termino_t*)estructura_termino;
	if ( !considerar_termino(aux) ) return false;
	ID_documento=(char*)ID_documento;
	char* linea = pasar_termino_a_string(termino,aux->repeticiones,ID_documento);
	
	//Si hubo un error con el termino
	if (!linea) fprintf(archivo,".ERROR");
	else{
		/*
		 * Agrego dos 1 despues de la linea escrita porque indica la cantidad
		 * de documentos en los que aparece el termino (1 mientras se parsee
		 * documento a documento) y uno que necesito para una funcion peso.
		 */
		fprintf(archivo,"%s1\n1\n",linea);
		free(linea);
	}
	
	return true;
	
}

double calcular_MI(int nro_de_terminos_en_documento,int frecuencia_conjunta, double producto_de_frecuencias_marginales,int cant){
	
	return (double)(log( ( (double) (pow(nro_de_terminos_en_documento,cant-1))* frecuencia_conjunta) / producto_de_frecuencias_marginales ) / log(2));



}

bool determinar_MI_de_termino(const char* termino, void* estructura_termino, void* cantidad_total_de_terminos, void* arbol){
	
	long* total=(long*)cantidad_total_de_terminos;
	termino_t* estructura = (termino_t*)estructura_termino;
	
	abb_avl_t* avl=(abb_avl_t*)arbol;
	
	//Si es una palabra sola, no calculamos mi.
	if(lista_largo(estructura->componentes)==1){
		estructura->mi = MIN_MI;
		return false;
	}
		
	int repeticiones = *(estructura->repeticiones);

	//Calculo el producto de las repeticiones de los componentes
	lista_iter_t* iter = lista_iter_crear(estructura->componentes);
	double producto = 1;
	int cantidad_de_componentes = 0;
	while(!lista_iter_al_final(iter)){
		char* clave_componente = (char*)lista_iter_ver_actual(iter);
		termino_t* componente = avl_obtener(avl,clave_componente);
		int rep_componente = *componente->repeticiones;
		producto = producto*rep_componente;
		lista_iter_avanzar(iter);
		cantidad_de_componentes++;
	}

	lista_iter_destruir(iter);
	
	estructura->mi = calcular_MI(*total,repeticiones,producto,cantidad_de_componentes);
	return true;
}

/*	
	Vuelca el avl a un subarchivo, con el formato que discutimos (termino,repeticiones).
*/
int volcar_avl_en_subarchivo(abb_avl_t* avl, int* nro_de_orden_del_subarchivo, long* cantidad_total_de_terminos){
	
	// Obtenemos el nombre del subarchivo a usar segun el numero de orden:
	FILE* subarchivo=obtener_subarchivo(nro_de_orden_del_subarchivo);
	if (!subarchivo){
		printf ("ERROR DE LECTURA DEL DISCO");
		return ERR_LECT;
	}
	
	// Itero el arbol calculando MI
	avl_in_order(avl,determinar_MI_de_termino,cantidad_total_de_terminos,avl);
	
	// Paso el ID del documento a string
	char* str_id = (char*)malloc(20*sizeof(char));
	sprintf(str_id,"%d",*nro_de_orden_del_subarchivo);
	
	// Pasamos a subarchivo
	avl_in_order(avl,pasar_termino_a_subarchivo,subarchivo,str_id);
	free(str_id);
	
	// Subarchivo ya procesado
	incrementar_entero(nro_de_orden_del_subarchivo);
	
	// Vacio el avl
	avl_destruir(avl);
	fclose(subarchivo);
	
	return 0;

}

/*	
	Actualiza las repeticiones para el termino en el avl
	La cuenta la manejo con int*, asi cuando quiero actualizar solo hago un obtener
	y de ahi cambio a donde apunta ese puntero
*/
void actualizar_avl(abb_avl_t* avl, termino_t* termino, char* ruta_archivo, long* cantidad_de_memoria_usada, long* cantidad_total_de_terminos){
	
	if (!avl_pertenece(avl,termino->valor)){
			
		/**
		 * TODO: La cantidad de memoria usada no se tiene que incrementar en 1
		 * se tiene que incrementar el tamaño de lo que sea que trabajes en memoria.
		 * No es lo mismo tener 1000 terminos de 1 caracter que 1000 terminos de 20.
		 **/
		 //TODO INCREMENTAR MEMORIA DE TERMINO_T
		
		//incrementar_memoria(cantidad_de_memoria_usada,strlen(termino));
		// incrementar_memoria(cantidad_de_memoria_usada,1);
		
		incrementar_entero(termino->repeticiones);
		incrementar_long(cantidad_total_de_terminos);
		avl_guardar(avl,termino->valor,termino); 

	}
	else{
		termino_t* termino_existente = (termino_t*)avl_obtener(avl, termino->valor);
		//Destruyo el termino porque ya existia de antes
		termino_destruir(termino);
		int* repeticiones = termino_existente->repeticiones;
		incrementar_entero(repeticiones);
		
	}
}


char* get_filename(char* path){

	char* filename = calloc(strlen(path) + 1, sizeof(char));
	if (! filename ){
		return NULL;
	}
	int index = 0;

	for (int i = 0 ; i < strlen(path) ; i++){

		if (path[i] == '/'){
			index = 0;
			continue;
		}

		filename[index] = path[i];
		index++;


	}
	

	if (strlen(filename)>3)filename[index-4] = '\0';

	return filename;

} 

//LUCAS 20-11-12: lo desactivo por ahora (ver parsear_archivo)

void process_filename(char* ruta_archivo,int max_words_per_term, abb_avl_t* avl, abb_avl_t* avl_stop_words, long* cantidad_total_de_terminos){


	char* line = get_filename(ruta_archivo);
	
	//Esto es un bug del leer linea, lo mejor seria solucionarlo ahi
	if (!line || strcmp(line,"")==0 || strcmp(line,"\n")==0 || strcmp(line,"\r")==0  || strcmp(line,"\r\n")==0){
		cadena_destruir(line);
		return;	
	}

	lista_t* lista_de_terminos = separar_cadena_en_terminos(line,max_words_per_term,avl_stop_words);

	//Iteramos la lista para ir sacando los terminos
	lista_iter_t* iter = lista_iter_crear(lista_de_terminos);
	
	//variable necesaria por API existente, deberia removerse lo de max memoria usada en futuro clean.
	long int cantidad_de_memoria_usada = 0; 

	while(!lista_iter_al_final(iter)){
		termino_t* termino = (termino_t*)lista_iter_ver_actual(iter);
		*(termino->repeticiones)= *(termino->repeticiones)+5000;
		
		actualizar_avl(avl,termino,ruta_archivo,&cantidad_de_memoria_usada,cantidad_total_de_terminos);
		
		lista_iter_avanzar(iter); 
	 }	 
	 lista_iter_destruir(iter);
	 
	 lista_destruir(lista_de_terminos,NULL);
	 cadena_destruir(line);

} 



				
/* 
 *	@params: un archivo de texto a parsear, un tamaño de memoria maximo, y el ID del subarchivo por el que vamos
 *
 *	@que hace?: extrae los terminos y los carga en el avl. Cuando se alcanza la max memoria,
 *	escribe los subarchivos que luego hay que mergear.
 *
 *  @returns: el nuevo numero de orden del subarchivo siguiente.
 */
int parsear_archivo(char* ruta_archivo,long memoria_disponible, int ID_del_subarchivo, int cantidad_maxima_de_palabras_por_termino,int stopwords){
	
	long cantidad_de_memoria_usada=0;
	long cantidad_total_de_terminos=0;
	FILE* archivo = fopen(ruta_archivo,"r");
	if (!archivo){
		printf ("ERROR DE LECTURA DEL DISCO");
		return ERR_LECT;
	}
	abb_avl_t* avl = avl_crear(strcmp, wrapper_destruir_termino);
	abb_avl_t* avl_stop_words = NULL;

	if (stopwords == 0)
		avl_stop_words = cargar_stop_words();
	
	//process_filename(ruta_archivo, cantidad_maxima_de_palabras_por_termino, avl, avl_stop_words, &cantidad_total_de_terminos);	
	//Leemos mientras no se termine el archivo (en ese caso leer linea devuelve string vacio)
	while(!feof(archivo)){

		char* linea = leer_linea(archivo);

		//Esto es un bug del leer linea, lo mejor seria solucionarlo ahi
		if (strcmp(linea,"")==0 || strcmp(linea,"\n")==0 || strcmp(linea,"\r")==0  || strcmp(linea,"\r\n")==0){
			cadena_destruir(linea);
			continue;	
		}
		
		lista_t* lista_de_terminos = separar_cadena_en_terminos(linea,cantidad_maxima_de_palabras_por_termino,avl_stop_words);
		
		//Iteramos la lista para ir sacando los terminos
		lista_iter_t* iter = lista_iter_crear(lista_de_terminos);
		 
		 while(!lista_iter_al_final(iter)){
		 	termino_t* termino = (termino_t*)lista_iter_ver_actual(iter);
		 	//Si se lleno la memoria
			if (cantidad_de_memoria_usada >= memoria_disponible){
				volcar_avl_en_subarchivo(avl,&ID_del_subarchivo,&cantidad_total_de_terminos);
				avl = avl_crear(strcmp,wrapper_destruir_termino);	
				cantidad_de_memoria_usada=0;
				cantidad_total_de_terminos=0;
				actualizar_avl(avl,termino,ruta_archivo,&cantidad_de_memoria_usada,&cantidad_total_de_terminos);
			}
			else{
				actualizar_avl(avl,termino,ruta_archivo,&cantidad_de_memoria_usada,&cantidad_total_de_terminos);
			}
			
			lista_iter_avanzar(iter); 
		 }	 
		 lista_iter_destruir(iter);
		 
		 lista_destruir(lista_de_terminos,NULL);
		 cadena_destruir(linea);
		

	}	

	volcar_avl_en_subarchivo(avl,&ID_del_subarchivo,&cantidad_total_de_terminos);
	if (avl_stop_words) avl_destruir(avl_stop_words);
	fclose(archivo);
	return ID_del_subarchivo;

	
	
}



/* WARNING! DE ACA PARA ABAJO SON FUNC. AUX PARA PROBAR EL CALCULO DE MI!!!! 
/
	NO USARLAS PARA NADA!*/



bool pasar_MI_a_vector(const char* termino, void* estructura_termino, void* vector, void* indice){

	double* array= (double*)vector;
	int* i=(int*)indice;
	
	termino_t* estructura = (termino_t*)estructura_termino;
	
	if(lista_largo(estructura->componentes)==1) return false;
	array[*i]=estructura->mi;
	*i=*i+1;
	return true;

}

/*	
	Vuelca el avl a un subarchivo, con el formato que discutimos (termino,repeticiones).
*/
double* volcar_avl_en_subarchivo_para_probar_mi(char* ruta_archivo, abb_avl_t* avl, int* nro_de_orden_del_subarchivo, long* cantidad_total_de_terminos){
	

	

	//Itero el arbol calculando MI
	avl_in_order(avl,determinar_MI_de_termino,cantidad_total_de_terminos,avl);
	

	double* vector = malloc(100*sizeof(double));
	
	int* i = malloc(sizeof(int));
	*i=0;
	
	avl_in_order(avl,pasar_MI_a_vector,vector,i);
	//vacio el avl
	avl_destruir(avl);
	free(i);

	return vector;

}
	

double* parsear_archivo_para_probar_mi(char* ruta_archivo,long memoria_disponible, int ID_del_subarchivo, int cantidad_maxima_de_palabras_por_termino){
	
	long cantidad_de_memoria_usada=0;
	long cantidad_total_de_terminos=0;
	FILE* archivo = fopen(ruta_archivo,"r");
	if (!archivo){
		printf ("ERROR DE LECTURA DEL DISCO");
		return NULL;
	}
	abb_avl_t* avl = avl_crear(strcmp, wrapper_destruir_termino);
	
	//Leemos mientras no se termine el archivo
	while(!feof(archivo)){
		char* linea = leer_linea(archivo);
		
		lista_t* lista_de_terminos = separar_cadena_en_terminos(linea,cantidad_maxima_de_palabras_por_termino,NULL);
		 
		//Iteramos la lista para ir sacando los terminos
		lista_iter_t* iter = lista_iter_crear(lista_de_terminos);
		 
		 while(!lista_iter_al_final(iter)){
		 	termino_t* termino = (termino_t*)lista_iter_ver_actual(iter);

		 	//Si se lleno la memoria
			if (cantidad_de_memoria_usada >= memoria_disponible){
				volcar_avl_en_subarchivo_para_probar_mi(ruta_archivo,avl,&ID_del_subarchivo,&cantidad_total_de_terminos);
				avl = avl_crear(strcmp,wrapper_destruir_termino);	
				cantidad_de_memoria_usada=0;
				cantidad_total_de_terminos=0;
				actualizar_avl(avl,termino,ruta_archivo,&cantidad_de_memoria_usada,&cantidad_total_de_terminos);
			}
			else{
				actualizar_avl(avl,termino,ruta_archivo,&cantidad_de_memoria_usada,&cantidad_total_de_terminos);
			}
			
			lista_iter_avanzar(iter); 
		 }	 
		 lista_iter_destruir(iter);
		 
		 lista_destruir(lista_de_terminos,NULL);
		 cadena_destruir(linea);	
	}
	
	fclose(archivo);
	return volcar_avl_en_subarchivo_para_probar_mi(ruta_archivo,avl,&ID_del_subarchivo,&cantidad_total_de_terminos);
	
}
